import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.io.FileReader;
import java.io.FileWriter;

/**
 * decoderCode implements the interface decoder. Provided by an input
 * file, decoderCode main purpose in to parse the instructions inside the 
 * provided file and load them for simulation. 
 */
public class Decoder implements IDecoder {
	/**
	 * SIZE represents the size of the array.
	 */
	private static final int SIZE = 256;
	
	/**
	 * instructionsArray is an array into which the parsed instructions are loaded.
	 */
	private static Word[] initialValuesArray = new Word[SIZE];
	
	/**
	 * HLENGTH represents the header length.
	 */
	private static final int HLENGTH = 13;
	
	/**
	 * RLENGTH represents a record text length.
	 */
	private static final int RLENGTH = 8;
	
	/**
	 * An object of type file is created for immutability  in manipulating the
	 * input file provided by the user. 
	 */
	private FileReader inputFile;
	
	/**
	 * An object of type file is created for immutability  in manipulating the
	 * report file provided by the user. 
	 */
	private FileWriter reportFile;	
	
	/** 
	 * We'll use a String as data structure for the instructions provided on
	 * each line of input file.
	 */

	/** 
	 * instruction represents an instruction on any given line in the input file.
	 */
	private String instruction = "";
	

	/**
	 * executionBegin represent a 2 Hex address at which execution is to begin.
	 */
	private String executionBegin = "";
	
	/** 
	 * segmentAdress represents a 2 Hex character value denoting the segment 
	 * load address.
	 */
	private String segmentAdress = "";
	
	/**
	 * segmentLength represents a 2 Hex character value denoting the length 
	 * of the segment. 
	 */
	private String segmentLength = "";
	
	/**
	 * value represents a 5 Hex character value.
	 */
	private int value;
	
	/**
	 * pc is the integer address at which execution is to begin.
	 */
	private int pc = 0;

	/**
	 * error is used to display the error to the standard output.
	 */
	private String error = "";
	
	/** 
	 * intructionsToParse takes the input file provided by the user
	 * as parameter and parse the instructions inside the file.
	 * intructionsToParse also takes the report file as a second parameter into
	 * which error generated while parsing the instructions in the input file
	 * are written. 
	 * @param inputfile represents the input file provided by the user
	 * @param reportfile report the report file in which error are to be written  
	 * @return Word[] represents an array of data containing the values 
	 * For simplicity, we'll assume that the memory of the W11-560 machine is 
	 * initially empty before loading the values.
	 */
	

	public final Word[] instructionsToParse(FileReader inputfile, FileWriter reportfile) {
		/* Initializing the array of word to zero before loading the initial
		 * values 
		 */
		for (int i = 0; i < SIZE; i++) {
		 initialValuesArray[i] = new Word(0);
		}
		
		// Defensive copy of the input and report files
		inputFile = inputfile;
		reportFile = reportfile;
		
		/* firstChar is used to check the validity of each first character
		 * on each line of the input file.
		 */
		Character firstChar;
		
		// index is used as the index for initialValuesArray
		int index = 0;
		
		/* line is used to read each line in the user input file */
		Scanner line = new Scanner(inputfile);
		
		/* Checking if the input file is not empty */
		if (!line.hasNextLine()) {
			messageError("Fatal Error \n The file " + inputfile 
				+ " provided is empty.\n Ending The Application.");
			System.exit(1);
		} else {
			
			/* Read the first line in the input file provided by the user */
			instruction = line.nextLine();
			
			/* Pull out the first character of the line to check whether it is
			 * a valid one or not.
			 */
			firstChar = instruction.charAt(0);			
			
			/* Check if the first character is the starting of a header or not */
			if ((firstChar.equals('h')) || (firstChar.equals('H'))) {
				/* Check the correct length is case the first character is of 
				 * type h or H indicating a header and then parse the line*/
				if (instruction.length() == HLENGTH) {
					executionBegin = instruction.substring(1, 3);
					segmentAdress = instruction.substring(9, 11);
					segmentLength = instruction.substring(11);
					//check the Hex characters validity
					if (charHexValidity(executionBegin) 
						&& charHexValidity(segmentAdress) 
						&& charHexValidity(segmentLength) ) {
						pc = Integer.parseInt(executionBegin, 16);
						while (line.hasNextLine()) {
							instruction = line.nextLine();
							firstChar = instruction.charAt(0);
							/* Check if the first character is a T or t and the length*/
							if (((firstChar.equals('T')) || (firstChar.equals('t'))) 
								&& (instruction.length() == RLENGTH) ) {
									if (charHexValidity(instruction.substring(1))) {
									// Storing the values in the array at correct index
										index = Integer.parseInt
										(instruction.substring(1, 3), 16);
										if ((0 <= index) && (index < (SIZE - 1))) {
											value = Integer.parseInt
											(instruction.substring(3), 16);
                                            // Sign extend value and add to memory
											initialValuesArray[index] = new Word(value << 12 >> 12);
										}
									} else {
										messageError("Warning: Skipping the instruction "
										+ instruction + " because is not a valid " 
										+ "Hex instruction.");
									}
							} else {
								messageError("Warning: Skipping the instruction "
								+ instruction + " because it is not a valid one."); 
							}
						}
					} else {
						error = "Fatal Error! \nInvalid Hex characters in" 
							+ " the Header " + instruction 
							+ "\nEnding The Application";
						messageError(error);
						writeReportFile(reportFile, error);
						System.exit(1);
					}
				}
			} else {
				error = "Fatal Error! \nInvalid Header format in the executable" 
					+ "input file.\nEnding The Application";
				messageError(error);
				writeReportFile(reportFile, error);
				System.exit(1);
			}
		}

		return initialValuesArray;
	}
	
	/** writeReportFile takes the report file as input and writes the error
	 * generated while trying to load the instructions from the input file.
	 * @param reportfile is the report file name provided by the user
	 * @param message is the error message to be written in the report file
	 */
	public final void writeReportFile(FileWriter reportfile, String message) {
		reportFile = reportfile;
		try {
			reportfile.write(message);
			reportfile.close();
		} catch (Exception e) {
			System.err.println("Unable to write to process output.\n" 
			+ e.getMessage());
		}
	}
	
	/**
	 * returnPC return the address at which execution is to begun.
	 * @return int return the address at which execution is to begun
	 */
	public final int returnPC() {
		return this.pc;
	}
	
	/**
	 * checkHex checks if a given k Hex character is a good one.
	 * @param str represent the character to check
	 * @return boolean indicating the correct range
	 */
	private boolean charHexValidity(String str) {
        try {
            Integer.parseInt(str, 16);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
	}
	
	/**
	 * meassgeError displays an error message to the standard output.
	 * @param errorOutput is of type String and contains the error to be displayed
	 */
	private void messageError(String errorOutput) {
	    System.err.println(errorOutput);
	}	
}
